package com.bob.like.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bob.like.constant.BlogConstant;
import com.bob.like.constant.MQRedisLuaScriptConstant;
import com.bob.like.constant.MoreRedisLuaScriptConstant;
import com.bob.like.constant.ThumbConstant;
import com.bob.like.manager.cache.CacheManager;
import com.bob.like.mapper.ThumbMapper;
import com.bob.like.model.dto.DoThumbRequest;
import com.bob.like.model.entity.Blog;
import com.bob.like.model.entity.Thumb;
import com.bob.like.model.entity.User;
import com.bob.like.model.enums.LuaStatusEnum;
import com.bob.like.mq.event.LikeMessage;
import com.bob.like.mq.event.ThumbEvent;
import com.bob.like.service.BlogService;
import com.bob.like.service.ThumbService;
import com.bob.like.service.UserService;
import com.bob.like.util.RedisKeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service("thumbService")
@Slf4j
@RequiredArgsConstructor
public class MoreThumbServiceImpl extends ServiceImpl<ThumbMapper, Thumb> implements ThumbService {
  
    private final UserService userService;

    private final BlogService blogService;
  
    private final RedisTemplate<String, Object> redisTemplate;

    private final CacheManager cacheManager;


    private final RocketMQTemplate rocketMQTemplate;

    private static final String LIKE_TOPIC = "Blog_Like_Topic";

    @Autowired
    private ResourceLoader resourceLoader;
    /**
     * 使用消息中间件优化、
     *
     * 改造成 QQ名片的点赞，每天最多点赞10次，晚上十二点进行更新
     *
     * @param doThumbRequest
     * @param request
     * @return
     */
    @Override
    public Boolean doThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        log.info("开始点赞,博客ID是：{}",doThumbRequest.getBlogId());
        if (doThumbRequest.getBlogId() == null) {
            throw new RuntimeException("参数错误");
        }
        User loginUser = userService.getLoginUser(request);
        //User loginUser = new User();
        //loginUser.setId(1L);
        Long blogId = doThumbRequest.getBlogId();
        //判断博客是否存在
        Object blog = cacheManager.get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        if (blog == null && getBlogCreateTime(blogId) == -1) {
            throw new RuntimeException("博客不存在");
        }

        String dateKey = DateUtil.today(); // yyyy-MM-dd
        String thumbCountKey = RedisKeyUtil.getUserDailyThumbCountKey(blogId, loginUser.getId(), dateKey);
        String thumbSumKey = RedisKeyUtil.getThumbSum(blogId.toString());
        String recentThumbKey = RedisKeyUtil.getRecentThumbKey(blogId);

        long timestamp = System.currentTimeMillis();

        // 加载并执行 Lua 脚本
        Long result = redisTemplate.execute(MQRedisLuaScriptConstant.THUMB_SCRIPT,
                Arrays.asList(thumbCountKey, thumbSumKey, recentThumbKey),
                timestamp,
                loginUser.getId(),
                ThumbConstant.THUMB_COUNT_LIMIT);
        if (LuaStatusEnum.FAIL.getValue() == result ) {
            throw new RuntimeException("您今天已经点赞了10次，请明天再来");
        }
        // 发送 RocketMQ 事件
        LikeMessage msg = LikeMessage.builder()
                .userId(loginUser.getId())
                .blogId(blogId)
                .count(1)
                .timestamp(System.currentTimeMillis())
                .eventId(UUID.randomUUID().toString())
                .build();

        //同步发送：这里使用 syncSend 进行同步发送，确保消息成功投递到MQ Broker后再返回。同步发送可以拿到发送结果，如果发送失败可以及时捕获异常。相比异步发送，同步发送虽然略增加延迟，但能更确定地保证消息送达，对于数据一致性要求较高的场景（如点赞计数不丢失）更合适。
        //失败处理：如果 syncSend 抛出异常（例如网络故障、Broker不可用等），需要进行失败处理。可以采取重试发送的机制，例如立即重试几次（间隔短暂延迟），或者将失败的消息记录到本地日志/数据库以便后续补偿处理
        try {
            rocketMQTemplate.syncSend(LIKE_TOPIC, msg);
            //rocketMQTemplate.syncSend(LIKE_TOPIC, msg);
        } catch (Exception e) {
            // 记录错误日志
            log.error("点赞消息发送失败，userId={}, blogId={}, error={}", loginUser.getId(), blogId, e.getMessage());
            // 可考虑重试或业务补偿，如将消息持久化备用
            //handleFailedMessage(msg) 可以将未发送成功的点赞事件暂存（例如存入一个专门的数据库表或发送到备用队列）以便稍后重试或人工干预，确保数据最终不会丢失。
            //handleFailedMessage(msg);
            throw e; // 或根据业务选择是否抛出
        }

        // 更新成功才执行
        return true;
    }
    /**
     * 未使用多级缓存优化
     * @param doThumbRequest
     * @param request
     * @return
     */
    //@Override
    public Boolean doThumbNoMQ(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {  
            throw new RuntimeException("参数错误");  
        }  
        //User loginUser = userService.getLoginUser(request);
        User loginUser = new User();
        loginUser.setId(1L);
        Long blogId = doThumbRequest.getBlogId();
        //判断博客是否存在
        //Object blog = redisTemplate.opsForValue().get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        Object blog = cacheManager.get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        //Blog blog = blogService.getById(blogId);
        if (blog == null && getBlogCreateTime(blogId) == -1) {
            throw new RuntimeException("博客不存在");
        }

        String timeSlice = getTimeSlice();  
        // Redis Key  
        String tempThumbKey = RedisKeyUtil.getTempThumbKey(timeSlice);
        //String userThumbKey = RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId);
  
         //执行 Lua 脚本
        long result = redisTemplate.execute(
                MoreRedisLuaScriptConstant.THUMB_SCRIPT,
                Arrays.asList(tempThumbKey),
                loginUser.getId(),
                blogId
        );
  
        // 更新成功才执行  
        return LuaStatusEnum.SUCCESS.getValue() == result;
    }

    /**
     * 使用多级缓存优化
     */
    //@Override
    public Boolean doThumbMore(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {
            throw new RuntimeException("参数错误");
        }
        //User loginUser = userService.getLoginUser(request);
        User loginUser = new User();
        loginUser.setId(1L);
        Long blogId = doThumbRequest.getBlogId();
        //判断博客是否存在
        //Object blog = redisTemplate.opsForValue().get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        Object blog = cacheManager.get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        if (blog == null) {
            throw new RuntimeException("博客不存在");
        }

        String timeSlice = getTimeSlice();
        // Redis Key
        String tempThumbKey = RedisKeyUtil.getTempThumbKey(timeSlice);
        //String userThumbKey = RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId);

        // 点赞记录存入 Redis
        String hashKey = ThumbConstant.TEMP_THUMB_KEY_PREFIX.formatted(timeSlice);
        String fieldKey = loginUser.getId() + ":" + blogId;
        int cacheValue = cacheManager.get(hashKey, fieldKey);
        long result = LuaStatusEnum.FAIL.getValue();
        //当本地缓存中不存在时，则通过 lua 脚本去更新 redis 中的点赞记录
        if (cacheValue != 1) {
            // 执行 Lua 脚本
            result = redisTemplate.execute(
                    MoreRedisLuaScriptConstant.THUMB_SCRIPT,
                    Arrays.asList(tempThumbKey),
                    loginUser.getId(),
                    blogId
            );
        }
        // 本地缓存中缓存 key-value，key 是时间+userId:blogId，value 是点赞数。
        // 当点赞数更新的时候，本地缓存如何更新？
        // 以及如何读取到本地缓存的点赞数去更新数据库？
        // 以及当某个用户的点赞记录成为热点key，本地缓存中更新了，那么数据库中的点赞数应该如何更新？


        // 更新成功才执行
        return LuaStatusEnum.SUCCESS.getValue() == result;
    }
    @Override  
    public Boolean undoThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {  
//        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {
//            throw new RuntimeException("参数错误");
//        }
//        User loginUser = userService.getLoginUser(request);
//
//        Long blogId = doThumbRequest.getBlogId();
//        // 计算时间片
//        String timeSlice = getTimeSlice();
//        // Redis Key
//        String tempThumbKey = RedisKeyUtil.getTempThumbKey(timeSlice);
//        String userThumbKey = RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId);
//
//        // 执行 Lua 脚本
//        long result = redisTemplate.execute(
//                RedisLuaScriptConstant.UNTHUMB_SCRIPT,
//                Arrays.asList(tempThumbKey, userThumbKey),
//                loginUser.getId(),
//                blogId
//        );
//        // 根据返回值处理结果
//        if (result == LuaStatusEnum.FAIL.getValue()) {
//            throw new RuntimeException("用户未点赞");
//        }
//        return LuaStatusEnum.SUCCESS.getValue() == result;
        return true;
    }  
  
    private String getTimeSlice() {
        DateTime nowDate = DateUtil.date();
        // 修改当前时间的秒数为 0
        nowDate.setField(java.util.Calendar.SECOND, 0);
        // 修改当前时间的分钟数为最近的整十分钟
        int minute = nowDate.minute();
        nowDate.setField(java.util.Calendar.MINUTE, (minute / 10) * 10);
        return DateUtil.format(nowDate, "HH:mm:ss");
    }

    @Override
    public Boolean hasThumb(Long blogId, Long userId) {
        //return redisTemplate.opsForHash().hasKey(ThumbConstant.USER_THUMB_KEY_PREFIX + userId, blogId.toString());
        return ObjectUtil.isNotNull(redisTemplate.opsForValue().get(RedisKeyUtil.getUserThumbKey(userId,blogId))) ;
    }

    public long getBlogCreateTime(long blogId) {
        Object blogCreateTime = redisTemplate.opsForValue().get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        if(blogCreateTime == null){
            Blog blog = blogService.getById(blogId);
            if(ObjectUtil.isNull(blog)){
                return -1;
            }
            blogCreateTime = blog.getCreateTime().getTime();
            redisTemplate.opsForValue().set(BlogConstant.BLOG_KEY_PREFIX + blogId, blogCreateTime, BlogConstant.BLOG_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        }
        return blogCreateTime instanceof Long ? (Long) blogCreateTime : Long.parseLong(blogCreateTime.toString());
    }
}
